#include <stdarg.h>
#include "task.h"
#include "pm.h"
#include "stdio.h"
#include "memory.h"

#undef DEBUG_INFO
#define DEBUG_WARN
#define DEBUG_ERROR

#ifdef DEBUG_INFO
#define KDBGINFO kprintf("%s:%s:%u:", __FILE__, __FUNCTION__, __LINE__); kprintf
#else
#define KDBGINFO //
#endif
#ifdef DEBUG_WARN
#define KDBGWARN kprintf("%s:%s:%u:", __FILE__, __FUNCTION__, __LINE__); kprintf
#else
#define KDBGWARN //
#endif
#ifdef DEBUG_ERROR
#define KDBGERROR kprintf("%s:%s:%u:", __FILE__, __FUNCTION__, __LINE__); kprintf
#else
#define KDBGERROR //
#endif

sched_pri ____sched_pri[SCHED_PRICNT];

// threads are linked and ordered according to expected wake time.
sched_thread *____sched_threadSleeping = 0;
sched_thread *____sched_threadExpired = 0;

// threads are stored in a adjustable array.
sched_thread ____sched_threads[SCHED_THREADARRAY_FREE_MAX];
unsigned long ____sched_threads_cnt = SCHED_THREADARRAY_FREE_MAX;
unsigned long ____sched_threads_cntUsed = 0;

sched_thread *____sched_curthread = 0;
unsigned long ____sched_curcr3 = 0;
SCHED_PRI 	  ____sched_curpri = 0;

unsigned long ____sched_ticks = 0;
unsigned long ____sched_jiffies = 0;
unsigned long ____sched_jiffies_pertick = 100;
unsigned long ____sched_jiffies_perthread = 100;

// @common initialization
int sched_initialize()
{
	for(unsigned int x = 0; x < SCHED_PRICNT; ++x)
	{
		____sched_pri[x].cnt = 0;
		____sched_pri[x].active = 0;
	}
	for(unsigned int x = 0; x < SCHED_THREADARRAY_FREE_MAX; ++x)
	{
		____sched_threads[x].ustack = 0;
	}
	/// create a thread for the current running context (this one before multi-tasking is enabled!)
	// I assume the first free slot will be used. =)
	____sched_curthread = &____sched_threads[0];

	sched_threadDescription td;
	memset(&td, 0, sizeof(td));
	td.entry = 0;
	td.contextId = 0;
	td.processId = 0;
	td.priority = 0;
	td.ustack = pm_alloc() + 4096;
	td.kstack = 0;
	td.cs = 0x08;
	td.ds = 0x10;
	td.eflags = SCHED_EFLAGS32;
	td.medaddr = 0;
	sched_create_thread(&td, 0);
	return 1;
}

/// @old functionality
static bool ____sched_mt = false;
bool mtenabled()
{
	return ____sched_mt;
}
void mtdisable()
{
	____sched_mt = false;
}
void mtenable()
{
	____sched_mt = true;
}
// set the current process' registers
int SetRegs(int pid, struct regs *r)
{
	kprintf("(obsolete function used) %s\n", __FUNCTION__);
	return 1;
}
// kill the current process using the PID
int kill(int pid)
{
	kprintf("(obsolete function used) %s\n", __FUNCTION__);
	____sched_threads[pid].flags |= SCHED_THREAD_KILLED;
	return 1;
}
// gets the current pid
int currpid()
{
	kprintf("(obsolete function used) %s\n", __FUNCTION__);
	return (int)____sched_curthread->threadId;
}

/// @common functionality
// entry for any newly created thread; allows address space transistion so user stack can be accessed directly.
static void sched_threadEntry(unsigned long entry, unsigned long cs, unsigned long ds, 
					unsigned long ustack, unsigned long kstack, unsigned long eflags, 
					unsigned long count, ...)
{
	va_list ap;
	va_start( ap, count );
	KDBGINFO("entry:%x count:%x cs:%x ds:%x eflags:%x\n", entry, count, cs, ds, eflags);
	// setup kernel stack as user stack (we have them actually swapped right _now_ anyway!)
	sched_stackTemplate *ste = (sched_stackTemplate*)(ustack - sizeof(sched_stackTemplate) - (count+1) * 4);
	unsigned long *bste32 = (unsigned long*)(ustack - (count+1) * 4);
	bste32[1] = 0x0;			// __cdelc calling convention needs a caller address pushed onto the stack.
	for(unsigned int y = 0; y < count; ++y)
	{
		bste32[2+y] = va_arg(ap, unsigned long);
	}
	ste->gs = ds;
	ste->ds = ds;
	ste->es = ds;
	ste->fs = ds;
	ste->cs = cs;
	ste->eflags = eflags;
	ste->eip = entry;
	pm_free(____sched_curthread->kstack);
	____sched_curthread->kstack = kstack;
	____sched_curthread->ustack = (unsigned long)ste;
	/// This is uncharted territory as I have to rely on GCC not to optimize this into not working! Beware!!!
	// I am mimicing the instructions in irq.inc - as they resume the interrupted task. (I have interrupts disabled until EFLAGS gets poped).
	asm(" \
		cli; \
		nop; \
		nop; \
		movl %0, %%esp; \
		pop %%gs; \
		pop %%fs; \
		pop %%es; \
		pop %%ds; \
		popa; \
		addl $8, %%esp; \
		iret; \
		" : : "m" (____sched_curthread->ustack) );
	// We should never arrive here, unless pigs are flying or compilers are not emitting the IRET instruction.. =)
	for(;;);
	return;
}

/*
THREADID sched_create_thread( PROCESSID processID, unsigned long entry, SCHED_PRI pri, 
					unsigned long ustack, unsigned long kstack,
					unsigned short cs, unsigned short ds, unsigned long cr3,
					unsigned long eflags, unsigned long argCount, ...)
*/

// create a thread. (kernel thread use zero for processID) (uses a _cdelc calling convention)
THREADID sched_create_thread( sched_threadDescription *desc, unsigned long argCount, ...)
{
	va_list ap;
	va_start( ap, argCount );

	// find free slot in thread array
	for(unsigned int x = 0; x < ____sched_threads_cnt; ++x)
	{
		// determine if slot is used by knowing that we never use a thread with a stack at memory address zero.
		if(!____sched_threads[x].ustack)
		{
			KDBGINFO("found free thread slot; creating.\n");
			____sched_threads[x].processId = desc->processId;
			____sched_threads[x].priority = desc->priority;
			____sched_threads[x].threadId = x;
			// we (really) start on the kernel stack (since) we know we can access the memory it points to.
			// (afterwards) when thread is started it does not jump (directly) to entry, but to a special
			// routine here in the scheduler (which) then sets up the user stack and switchs: ustack and kstack.
			____sched_threads[x].ustack = desc->kstack;
			____sched_threads[x].kstack = pm_alloc() + 4096; // we free this in just a few moments..
			____sched_threads[x].flags = 0;
			____sched_threads[x].cr3 = desc->contextId;
			// link into thread class with existing threads; if any.
			if(!____sched_pri[desc->priority].active)
			{
				____sched_pri[desc->priority].active = &____sched_threads[x];
				____sched_threads[x].next = &____sched_threads[x];
				____sched_threads[x].prev = &____sched_threads[x];
			}else{
				____sched_threads[x].next = ____sched_pri[desc->priority].active;
				____sched_threads[x].prev = ____sched_pri[desc->priority].active->prev;

				____sched_pri[desc->priority].active->prev = &____sched_threads[x];
				____sched_threads[x].prev->next = &____sched_threads[x];
			}
			// prepare user stack for entry.
			sched_stackTemplate *ste = (sched_stackTemplate*)
					(____sched_threads[x].ustack - sizeof(sched_stackTemplate) - (argCount+7) * 4);
			unsigned long *bste32 = (unsigned long*)(____sched_threads[x].ustack - (argCount+7) * 4);
			bste32[1] = desc->entry;
			bste32[2] = desc->cs;
			bste32[3] = desc->ds;
			bste32[4] = desc->ustack;
			bste32[5] = desc->kstack;
			bste32[6] = desc->eflags;
			bste32[7] = argCount;
			for(unsigned int y = 0; y < argCount; ++y)
			{
				bste32[8+y] = va_arg(ap, unsigned long);
			}
			// We will be in kernel mode, until sched_threadEntry is completed.
			ste->eax = desc->registers.eax; ste->ebx = desc->registers.ebx; ste->ecx = desc->registers.ecx; 
			ste->edx = desc->registers.edx; ste->ebp = desc->registers.ebp; ste->esi = desc->registers.esi;
			ste->edi = desc->registers.edi;
			ste->gs = SCHED_KERNEL_DATADESC;
			ste->ds = SCHED_KERNEL_DATADESC;
			ste->es = SCHED_KERNEL_DATADESC;
			ste->fs = SCHED_KERNEL_DATADESC;
			ste->cs = SCHED_KERNEL_CODEDESC;
			ste->eflags = 0x200;
			if(desc->medaddr != 0)
			{
				// use a defined function for transitioning into address space, and possibly out of kernel mode.
				ste->eip = desc->medaddr;
			}else{
				ste->eip = (unsigned long)&sched_threadEntry;
			}
			____sched_threads[x].ustack = (unsigned long)ste;
			return (THREADID)x;
		}
	}
	KDBGWARN("out of thread slots.. code not implemented\n");
	return (THREADID)-1;
}

extern unsigned long tick;

/// performs a thread switch only if a certain amount of block ticks have passed.
void sched_switch(void)
{
	++____sched_ticks;
	if(____sched_ticks < ____sched_jiffies_pertick)
	{
		return;
	}
	____sched_ticks = 0;
	sched_switch_core();
}

/// performs a thread switch regardless of how many clock ticks have passed.
void sched_switch_core()
{
	/// @debug: just making sure we are running..
	unsigned short *vmem = (unsigned short*)0xb8000;
	++vmem[0];


	/// @todo: has current thread expired it's current slice of time?
	//if(tick * ____sched_jiffies_pertick >= ___sched_jiffies_perthread)
	//{
		/// @todo: implement priority boost.	
	//}	
	
	// have we been killed?
	if(____sched_curthread->flags & SCHED_THREAD_KILLED)
	{
		KDBGWARN("thread was killed:%x\n", ____sched_curthread);
		// unlink current thread
		____sched_curthread->next->prev = ____sched_curthread->prev;
		____sched_curthread->prev->next = ____sched_curthread->next;
		// we should have been the active thread; if so: rotating the thread list will completely remove us.
		if(____sched_curthread->next == ____sched_curthread)
		{
			____sched_pri[____sched_curpri].active = 0;
		}
	}

	// rotate thread list (newer move forward)
	if(____sched_pri[____sched_curpri].active)
	{
		____sched_pri[____sched_curpri].active = ____sched_pri[____sched_curpri].active->next;
	}

	// look for highest priority thread first.
	for(unsigned int x = 0; x < SCHED_PRICNT; ++x)
	{
		if(____sched_pri[x].active)
		{
			____sched_curthread = ____sched_pri[x].active;
			____sched_curpri = x;
			KDBGINFO("continuation-thread:%x [ustack:%x kstack:%x priority:%x]\n", ____sched_curthread, ____sched_curthread->ustack, ____sched_curthread->kstack, ____sched_curthread->priority);
			return;
		}
	}
	// no threads? - we should have had a idle one. (lock CPU)
	KDBGERROR("??no idle thread??\n");
	for(;;);
	return;
}










